MCP (Model Context Protocol) server for AI agent payment automation. Enables Claude Desktop, Claude API, and ChatGPT to execute payments via Ra Pay CLI.
Status: Perplexity Security Review APPROVED (98% confidence)
- 6 MVP tools for payment operations
- Subprocess isolation (credentials never leave keyring)
- Response sanitization (prevents prompt injection)
- Rate limiting (1 payment/min, 10 queries/min)
- Audit logging
- Node.js 18+
- Ra Pay CLI installed and authenticated (
ra link-bank)
cd rapay/mcp-server
npm install
npm run buildmacOS: ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"rapay": {
"command": "node",
"args": ["/Users/yourname/rapay/mcp-server/dist/index.js"]
}
}
}Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"rapay": {
"command": "node",
"args": ["C:\\Users\\yourname\\rapay\\mcp-server\\dist\\index.js"]
}
}
}With custom CLI path:
{
"mcpServers": {
"rapay": {
"command": "node",
"args": ["/path/to/rapay/mcp-server/dist/index.js"],
"env": {
"RAPAY_CLI_PATH": "/custom/path/to/ra"
}
}
}
}After adding, restart Claude Desktop. You should see "rapay" in the MCP servers list.
| Tool | Description |
|---|---|
ra_send | Execute a payment transaction |
ra_subscribe | Create a subscription for a customer |
ra_refund | Open Stripe Dashboard for refunds |
| Tool | Description |
|---|---|
ra_balance | Check available balance |
ra_history | Get transaction history |
ra_whoami | Check account status |
MCP server spawns Ra Pay CLI as subprocess. Credentials remain in OS keyring - MCP server never sees them directly.
All CLI output is sanitized to prevent prompt injection:
- ANSI escape sequences removed
- System markers filtered (
[SYSTEM],[USER], etc.) - Control characters stripped
Defense-in-depth layer at MCP level:
| Tool | Limit |
|---|---|
ra_send | 1 per 60 seconds |
ra_subscribe | 1 per 60 seconds |
ra_refund | 5 per 60 seconds |
ra_balance | 10 per 60 seconds |
ra_history | 10 per 60 seconds |
ra_whoami | 20 per 60 seconds |
Note: Backend also enforces velocity controls (account-tier daily limits).
Ra Pay is designed as a "dumb pipe" to Stripe:
What Ra Pay stores:
- Your user ID
- Your Stripe account ID (encrypted)
- Action logs: "payment sent", "balance checked" (no amounts)
- Transaction audit trail with Stripe transfer IDs
What Ra Pay does NOT store:
- Your payment amounts
- Recipient details
- Payment descriptions
- Your account balance
- Any personally identifiable information (Stripe handles KYC)
What MCP server adds:
- Client type tracking ("called via Claude Desktop")
- Tool call audit logs (same privacy level as above)
- No new PII storage
| Variable | Description | Default |
|---|---|---|
RAPAY_CLI_PATH | Path to Ra Pay CLI executable | ra |
Logs are written to ~/.rapay/mcp-audit.log with 7-day retention:
- Tool name, timestamp, duration
- Result (success/error/rate_limited)
- Sanitized inputs (amounts redacted, emails masked)
| Code | Description | Retryable |
|---|---|---|
RATE_LIMIT_EXCEEDED | MCP rate limit hit | No (wait) |
CLI_NOT_FOUND | Ra Pay CLI not installed | No |
TOS_ACCEPTANCE_REQUIRED | ToS not accepted | No |
ACCOUNT_NOT_LINKED | Stripe account not linked | No |
VELOCITY_EXCEEDED | Daily limit exceeded | No |
TIMEOUT | Request timed out | Yes |
NETWORK_ERROR | Network connectivity issue | Yes |
EXECUTION_FAILED | Generic CLI error | No |
{
"error": "rate_limit_exceeded",
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please wait 60 seconds.",
"retry_after_seconds": 60,
"retryable": false
}{
"error": "cli_not_found",
"code": "CLI_NOT_FOUND",
"message": "Ra Pay CLI not found. Please install it first.",
"retryable": false
}{
"error": "tos_required",
"code": "TOS_ACCEPTANCE_REQUIRED",
"message": "Terms of Service must be accepted. Run 'ra accept-tos' first.",
"retryable": false
}If you receive RATE_LIMIT_EXCEEDED, implement exponential backoff:
const maxRetries = 3;
let delay = 60; // seconds
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await mcp.callTool('ra_send', params);
} catch (error) {
if (error.code === 'RATE_LIMIT_EXCEEDED') {
console.log(Rate limited. Waiting <span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">delay</span><span class="pl-kos">}</span></span>s before retry...);
await sleep(delay 1000);
delay = 2; // exponential backoff
} else {
throw error;
}
}
}
// DO NOT:
// - Retry immediately (wastes time, still rate limited)
// - Retry more than 3 times (indicates genuine rate limit)
// - Ignore retry_after_seconds field
Note: MCP rate limiting is client-side defense-in-depth. Backend also enforces velocity controls per account tier.
You (Claude Desktop/API)
|
v
MCP Server (this package)
| - Logs tool calls (no amounts/PII)
| - Rate limits requests
| - Sanitizes responses
v
Ra Pay CLI (subprocess)
| - Credentials in OS keyring
| - Adds replay protection
v
Ra Pay Backend
| - Validates requests
| - Enforces velocity limits
v
Stripe API
| - Owns all PII
| - Processes payments
v
Recipient's Bank
All sensitive data flows directly to Stripe. Ra Pay only records that an action occurred.
This section documents the security posture of the published npm package.
| Category | Included | Excluded |
|---|---|---|
| Compiled JavaScript | Yes | - |
| TypeScript declarations | Yes | - |
| Source maps (.js.map) | No | Excluded for code privacy |
| Source code (src/) | No | Development only |
| Metadata | Value | Rationale |
|---|---|---|
| Repository | github.com/Ra-Pay-AI/rapay | Open source by design |
| Author | Ra Pay | Company name |
| License | MIT | Standard permissive license |
| Keywords | mcp, payments, stripe, claude | Discoverability |
These are documented for users and do not represent vulnerabilities:
- Rate limiting rules - Users need to know limits to implement backoff
- Error codes - Required for proper error handling
- Tool schemas - Required by MCP protocol specification
- Audit log location (~/.rapay/mcp-audit.log) - Users may need to inspect
| Category | Protection |
|---|---|
| API keys/secrets | Never in code (OS keyring only) |
| Backend URLs | Only public rapay.ai endpoints |
| User data | Subprocess isolation, never in MCP process |
| Payment amounts | Redacted as [redacted] in all logs |
| Email addresses | Masked (j***@example.com) in audit logs |
┌─────────────────────┐
│ AI Agent (Claude) │
└─────────┬───────────┘
│ MCP Protocol (stdio)
▼
┌─────────────────────┐
│ MCP Server (npm) │ ← No credentials here
│ - Rate limiting │
│ - Input validation │
│ - Response sanitize│
└─────────┬───────────┘
│ Spawns subprocess
▼
┌─────────────────────┐
│ Ra Pay CLI │ ← Credentials in OS keyring
│ - Session tokens │
│ - Stripe API calls │
└─────────────────────┘
The MCP server never has access to credentials. All sensitive operations are delegated to the CLI subprocess, which reads credentials directly from the OS keyring.
npm run dev # Watch mode
npm run build # Build
npm run lint # Lint
npm run test # TestMIT